﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Look_Through_Windows.Helper;

namespace Look_Through_Windows.Logic
{
    /// <summary>
    /// Segments an image for optimal compression (delta update)
    /// </summary>
    class SegmentTree : IEnumerable<SegmentTree>
    {
        /// <summary>
        /// Encode plain?
        /// </summary>
        public bool Plain;
        /// <summary>
        /// The actual buffer
        /// </summary>
        public byte[] Buffer;

        /// <summary>
        /// Children
        /// </summary>
        public List<SegmentTree> Children = new List<SegmentTree>( );

        /// <summary>
        /// Is a leaf node?
        /// </summary>
        public bool IsLeaf { get { return Children.Count == 0; } }

        /// <summary>
        /// Bounding box
        /// </summary>
        public Int32Rect AABB;

        /// <summary>
        /// Tha pixels
        /// </summary>
        public int[] Pixels;

        /// <summary>
        /// Depth
        /// </summary>
        public int Depth;

        /// <summary>
        /// Size in bytes, including header 
        /// </summary>
        public int ByteSize { get { return Buffer.Length + 4 * 10 + 1; } }

        private SegmentTree( int[] pixels, Int32Rect aabb )
        {
            Buffer = GetBuffer( pixels, aabb.Width, aabb.Height, out Plain );
            AABB = aabb;
            Pixels = pixels;
        }

        private void Segment( )
        {
            int minSize = ByteSize;
            SegmentTree minSub1 = null, minSub2 = null;

            // partition X
            bool lastEmpty = false;
            int lastEmptyX = 0;
            //Debug.Assert( Pixels.GetMinRect( AABB.Width, AABB.Height ).Width == AABB.Width );
            for ( int x = 1 ; x < AABB.Width ; ++x )
            {
                if ( sw.Elapsed.TotalSeconds > 0.3 )
                    break;

                bool empty = true;
                for ( int y = 0 ; y < AABB.Height ; ++y )
                    if ( Pixels[ y * AABB.Width + x ] != 0 )
                    {
                        empty = false;
                        break;
                    }

                if ( !empty && lastEmpty )
                {
                    Int32Rect leftAABB, rightAABB;
                    int[] left = Pixels.GetSubArray( leftAABB = new Int32Rect( 0, 0, lastEmptyX, AABB.Height ), AABB.Width, AABB.Height );
                    int[] right = Pixels.GetSubArray( rightAABB = new Int32Rect( lastEmptyX + 1, 0, AABB.Width - ( lastEmptyX + 1 ), AABB.Height ), AABB.Width, AABB.Height );

                    Int32Rect leftTruncAABB = left.GetMinRect( leftAABB.Width, leftAABB.Height );
                    left = left.GetSubArray( leftTruncAABB, leftAABB.Width, leftAABB.Height );
                    Int32Rect rightTruncAABB = right.GetMinRect( rightAABB.Width, rightAABB.Height );
                    right = right.GetSubArray( rightTruncAABB, rightAABB.Width, rightAABB.Height );

                    leftTruncAABB.X += AABB.X + leftAABB.X;
                    leftTruncAABB.Y += AABB.Y + leftAABB.Y;
                    rightTruncAABB.X += AABB.X + rightAABB.X;
                    rightTruncAABB.Y += AABB.Y + rightAABB.Y;

                    SegmentTree sub1 = new SegmentTree( left, leftTruncAABB );
                    SegmentTree sub2 = new SegmentTree( right, rightTruncAABB );
                    sub1.Depth = Depth + 1;
                    sub2.Depth = Depth + 1;

                    int size = sub1.ByteSize + sub2.ByteSize;
                    if ( size < minSize )
                    {
                        minSize = size;
                        minSub1 = sub1;
                        minSub2 = sub2;
                    }
                }

                if ( empty )
                {
                    lastEmptyX = x;
                }
                lastEmpty = empty;
            }

            // partition Y
            lastEmpty = false;
            int lastEmptyY = 0;
            //Debug.Assert( Pixels.GetMinRect( AABB.Width, AABB.Height ).Height == AABB.Height );
            for ( int y = 1 ; y < AABB.Height ; ++y )
            {
                if ( sw.Elapsed.TotalSeconds > 0.3 )
                    break;

                bool empty = true;
                for ( int x = 0 ; x < AABB.Width ; ++x )
                    if ( Pixels[ y * AABB.Width + x ] != 0 )
                    {
                        empty = false;
                        break;
                    }

                if ( !empty && lastEmpty )
                {
                    Int32Rect topAABB, bottomAABB;
                    int[] top = Pixels.GetSubArray( topAABB = new Int32Rect( 0, 0, AABB.Width, lastEmptyY ), AABB.Width, AABB.Height );
                    int[] bottom = Pixels.GetSubArray( bottomAABB = new Int32Rect( 0, lastEmptyY + 1, AABB.Width, AABB.Height - ( lastEmptyY + 1 ) ), AABB.Width, AABB.Height );

                    Int32Rect topTruncAABB = top.GetMinRect( topAABB.Width, topAABB.Height );
                    top = top.GetSubArray( topTruncAABB, topAABB.Width, topAABB.Height );
                    Int32Rect bottomTruncAABB = bottom.GetMinRect( bottomAABB.Width, bottomAABB.Height );
                    bottom = bottom.GetSubArray( bottomTruncAABB, bottomAABB.Width, bottomAABB.Height );

                    topTruncAABB.X += AABB.X + topAABB.X;
                    topTruncAABB.Y += AABB.Y + topAABB.Y;
                    bottomTruncAABB.X += AABB.X + bottomAABB.X;
                    bottomTruncAABB.Y += AABB.Y + bottomAABB.Y;

                    SegmentTree sub1 = new SegmentTree( top, topTruncAABB );
                    SegmentTree sub2 = new SegmentTree( bottom, bottomTruncAABB );
                    sub1.Depth = Depth + 1;
                    sub2.Depth = Depth + 1;

                    int size = sub1.ByteSize + sub2.ByteSize;
                    if ( size < minSize )
                    {
                        minSize = size;
                        minSub1 = sub1;
                        minSub2 = sub2;
                    }
                }

                if ( empty )
                {
                    lastEmptyY = y;
                }
                lastEmpty = empty;
            }

            // add and recurse
            if ( minSub1 != null )
            {
                Children.Add( minSub1 );
                Children.Add( minSub2 );

                if ( sw.Elapsed.TotalSeconds < 0.3 )
                    minSub1.Segment( );
                if ( sw.Elapsed.TotalSeconds < 0.3 )
                    minSub2.Segment( );
            }
        }

        public static byte[] GetBuffer( int[] pixels, int width, int height, out bool plain )
        {
            plain = false;
            byte[] pngbuffer = pixels.ToPNGByteArray( width, height );
            byte[] plainbuffer = null;

            if ( width * height < 50 * 1000 )
            {

            }

            return plain ? plainbuffer : pngbuffer;
        }

        private static Stopwatch sw = new Stopwatch( );
        public static SegmentTree Create( int[] pixels, int width, int height )
        {
            sw.Restart( );
            SegmentTree tree = new SegmentTree( pixels, new Int32Rect( 0, 0, width, height ) );
            tree.Segment( );
            return tree;
        }

        public IEnumerator<SegmentTree> GetEnumerator( )
        {
            if ( IsLeaf ) yield return this;
            else
            {
                foreach ( var child in Children )
                    foreach ( var leaf in child )
                        yield return leaf;
            }
        }

        IEnumerator IEnumerable.GetEnumerator( )
        {
            return GetEnumerator( );
        }
    }
}
